#include "EditorLayer.h"

#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
#include "imgui.h"
#include "platform/OpenGL/OpenGLShader.h"

const uint32_t mapWidth = 24;
const char *MapTitle = "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWLLLLWWWWWWWWWWWWWWWW"
                       "WWWWLLLLWWWWWWWWWWWWWWWW"
                       "WWWWLLLLWWWWWWWWWWWWWWWW"
                       "WWWWLLLLWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW"
                       "WWWWWWWWWWWWWWWWWWWWWWWW";
template <typename Fn> class Timer
{
  public:
    Timer(const char *name, Fn &&func)
        : m_Name(name), m_Stopped(false), m_StartTimepoint(std::chrono::high_resolution_clock::now()), m_func(func)
    {
    }

    void Stop()
    {
        auto endTimepoint = std::chrono::high_resolution_clock::now();
        long long start =
            std::chrono::time_point_cast<std::chrono::microseconds>(m_StartTimepoint).time_since_epoch().count();
        long long end =
            std::chrono::time_point_cast<std::chrono::microseconds>(endTimepoint).time_since_epoch().count();
        m_Stopped = true;

        float duration = (end - start) * 0.001f;
        m_func({m_Name, duration});
    }

    ~Timer()
    {
        if (!m_Stopped)
            Stop();
    }

  private:
    const char *m_Name;
    bool m_Stopped;

    std::chrono::time_point<std::chrono::steady_clock> m_StartTimepoint;
    Fn m_func;
};

#define PROFILE_SCOPE(name)                                                                                            \
    Timer timer##__LINE__(name, [&](ProfileResult profileresult) { m_ProfileResults.push_back(profileresult); });

namespace Hazel
{
EditorLayer::EditorLayer()
    : Hazel::Layer("EditorLayer"), m_CameraController(1280.0f / 720.0f), m_SquareColor({0.2f, 0.3f, 0.8f, 1.0f})
{
}

void EditorLayer::OnAttach()
{
    m_CheckboardTexture = Hazel::Texture2D::Create("../../assert/texture2d/checkboard.png");
    m_SpriteSheet =
        Hazel::Texture2D::Create("../../assert\\kenney_monochrome-pirates\\Default\\Tilemap\\tilemap_packed.png");
    m_TextureFlag = Hazel::SubTexture2D::CreateFromCoords(m_SpriteSheet, {7, 2}, {16, 16});
    m_TextureTree = Hazel::SubTexture2D::CreateFromCoords(m_SpriteSheet, {4, 1}, {16, 16}, {1, 2});

    m_MapElement['W'] = Hazel::SubTexture2D::CreateFromCoords(m_SpriteSheet, {1, 6}, {16, 16});
    m_MapElement['L'] = Hazel::SubTexture2D::CreateFromCoords(m_SpriteSheet, {4, 6}, {16, 16});

    m_CameraController.SetZoomLevel(0.25f);

    Hazel::FrameBufferSpecification fbspec;
    fbspec.Width = 1280;
    fbspec.Height = 720;
    m_FrameBuffer = Hazel::FrameBuffer::Create(fbspec);
}

void EditorLayer::OnDetach()
{
}

void EditorLayer::OnUpdate(Hazel::Timestep ts)
{
    PROFILE_SCOPE("EditorLayer::OnUpdate");
    // update
    {
        PROFILE_SCOPE("m_CameraController::OnUpdate");
        m_CameraController.OnUpdate(ts);
    }
    // render
    Hazel::Renderer2D::ResetStats();
    {
        PROFILE_SCOPE("Renderer Prep");
        m_FrameBuffer->Bind();
        Hazel::RendererCommand::SetClearColor({0.1f, 0.1f, 0.1f, 1});
        Hazel::RendererCommand::Clear();
    }
#if 0
    {
        static float rotation = 0.0f;
        rotation += ts * 20.0f;
        PROFILE_SCOPE("Renderer Draw");
        Hazel::Renderer2D::BeginScene(m_CameraController.GetCamera());

        glm::mat4 scale = glm::scale(glm::mat4(1.0f), glm::vec3(0.1f));
        Hazel::Renderer2D::DrawRotatedQuad({-1.0f, 0.0f}, {0.8f, 0.8f}, rotation, {0.8f, 0.2f, 0.3f, 1.0f});
        Hazel::Renderer2D::DrawQuad({-1.0f, 0.0f}, {0.8f, 0.8f}, {0.8f, 0.2f, 0.3f, 1.0f});
        Hazel::Renderer2D::DrawQuad({0.5f, -0.5f}, {0.5f, 0.75f}, {0.2f, 0.3f, 0.8f, 1.0f});
        Hazel::Renderer2D::DrawQuad({0.0f, 0.0f, -0.1f}, {10.0f, 10.0f}, m_CheckboardTexture, 10.0f);
        Hazel::Renderer2D::DrawRotatedQuad({-2.0f, -0.0f, 0.01f}, {1.0f, 1.0f}, rotation, m_CheckboardTexture, 10.0f);
        // Hazel::Renderer2D::EndScene();

        // Hazel::Renderer2D::BeginScene(m_CameraController.GetCamera());
        for (float y = -5.0f; y < 5.0f; y += 0.5f)
        {
            for (float x = -5.0f; x < 5.0f; x += 0.5f)
            {
                glm::vec4 color = {(x + 5.0f) / 10.0f, 0.4f, (y + 5.0f) / 10.0f, 0.5f};
                Hazel::Renderer2D::DrawQuad({x, y}, {0.45f, 0.45f}, color);
            }
        }

        Hazel::Renderer2D::EndScene();
    }
#endif

    Hazel::Renderer2D::BeginScene(m_CameraController.GetCamera());
    // Hazel::Renderer2D::DrawQuad({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f}, m_SpriteSheet);
    uint32_t mapHeight = strlen(MapTitle) / mapWidth;
    for (uint32_t x = 0; x < mapWidth; x++)
    {
        for (uint32_t y = 0; y < mapHeight; y++)
        {

            char type = MapTitle[x + y * mapWidth];
            auto it = m_MapElement.find(type);
            if (it == m_MapElement.end())
                Hazel::Renderer2D::DrawQuad({x - mapWidth / 2, y - mapHeight / 2, 0.0f}, {1.0f, 1.0f}, m_TextureFlag);
            else
                Hazel::Renderer2D::DrawQuad({(float)(x - mapWidth * 0.5f), -(float)(y - mapHeight * 0.5f), 0.0f},
                                            {1.0f, 1.0f}, it->second);
        }
    }
    // Hazel::Renderer2D::DrawQuad({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f}, m_TextureFlag);
    // Hazel::Renderer2D::DrawQuad({1.0f, 0.0f, 0.0f}, {1.0f, 2.0f}, m_TextureTree);
    Hazel::Renderer2D::EndScene();
    m_FrameBuffer->Unbind();
    // TODO: Add this functions - Shader::SetMat4,Shader::SetFloat4
    // std::dynamic_pointer_cast<Hazel::OpenGLShader>(m_FlatColorShader)->Bind();
    // std::dynamic_pointer_cast<Hazel::OpenGLShader>(m_FlatColorShader)->UploadUniformFloat4("u_Color", m_SquareColor);

    // Hazel::Renderer2D::Submit(m_SquareVA, m_FlatColorShader, glm::scale(glm::mat4(1.0f), glm::vec3(1.5f)));
    // Hazel::Renderer::Submit(m_VertexArray,m_Shader);
}

void EditorLayer::OnImGuiRender()
{
    // READ THIS !!!
    // TL;DR; this demo is more complicated than what most users you would normally use.
    // If we remove all options we are showcasing, this demo would become:
    //     void ShowExampleAppDockSpace()
    //     {
    //         ImGui::DockSpaceOverViewport(ImGui::GetMainViewport());
    //     }
    // In most cases you should be able to just call DockSpaceOverViewport() and ignore all the code below!
    // In this specific demo, we are not using DockSpaceOverViewport() because:
    // - (1) we allow the host window to be floating/moveable instead of filling the viewport (when opt_fullscreen ==
    // false)
    // - (2) we allow the host window to have padding (when opt_padding == true)
    // - (3) we expose many flags and need a way to have them visible.
    // - (4) we have a local menu bar in the host window (vs. you could use BeginMainMenuBar() + DockSpaceOverViewport()
    //      in your code, but we don't here because we allow the window to be floating)

    static bool dockSpaceOpen = true;
    static bool opt_fullscreen = true;
    static bool opt_padding = false;
    static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None;

    // We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into,
    // because it would be confusing to have two docking targets within each others.
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
    if (opt_fullscreen)
    {
        const ImGuiViewport *viewport = ImGui::GetMainViewport();
        ImGui::SetNextWindowPos(viewport->WorkPos);
        ImGui::SetNextWindowSize(viewport->WorkSize);
        ImGui::SetNextWindowViewport(viewport->ID);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
        window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
                        ImGuiWindowFlags_NoMove;
        window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
    }
    else
    {
        dockspace_flags &= ~ImGuiDockNodeFlags_PassthruCentralNode;
    }

    // When using ImGuiDockNodeFlags_PassthruCentralNode, DockSpace() will render our background
    // and handle the pass-thru hole, so we ask Begin() to not render a background.
    if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode)
        window_flags |= ImGuiWindowFlags_NoBackground;

    // Important: note that we proceed even if Begin() returns false (aka window is collapsed).
    // This is because we want to keep our DockSpace() active. If a DockSpace() is inactive,
    // all active windows docked into it will lose their parent and become undocked.
    // We cannot preserve the docking relationship between an active window and an inactive docking, otherwise
    // any change of dockspace/settings would lead to windows being stuck in limbo and never being visible.
    if (!opt_padding)
        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
    ImGui::Begin("DockSpace Demo", &dockSpaceOpen, window_flags);
    if (!opt_padding)
        ImGui::PopStyleVar();

    if (opt_fullscreen)
        ImGui::PopStyleVar(2);

    // Submit the DockSpace
    ImGuiIO &io = ImGui::GetIO();
    if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable)
    {
        ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
        ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags);
    }

    if (ImGui::BeginMenuBar())
    {
        if (ImGui::BeginMenu("file"))
        {
            // Disabling fullscreen would allow the window to be moved to the front of other windows,
            // which we can't undo at the moment without finer window depth/z control.
            // ImGui::MenuItem("Fullscreen", NULL, &opt_fullscreen);
            // ImGui::MenuItem("Padding", NULL, &opt_padding);
            ImGui::Separator();

            if (ImGui::MenuItem("Exit"))
                Hazel::Application::Get().Close();

            ImGui::Separator();

            ImGui::EndMenu();
        }
        ImGui::EndMenuBar();
    }
   
    ImGui::Begin("settings");

    auto stats = Hazel::Renderer2D::GetStats();
    ImGui::Text("Renderer2D stats:");
    ImGui::Text("Draw calls: %d", stats.DrawCalls);
    ImGui::Text("Quads: %d", stats.QuadCount);
    ImGui::Text("Vertices: %d", stats.GetTotalVertexCount());
    ImGui::Text("Indices: %d", stats.GetTotalIndexCount());
    ImGui::End();
     ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
    ImGui::Begin("viewport");
    ImVec2 viewportSize = ImGui::GetContentRegionAvail();
    if (m_ViewportSize != *((glm::vec2 *)&viewportSize))
    {
        m_ViewportSize = {viewportSize.x, viewportSize.y};
        m_FrameBuffer->Resize(m_ViewportSize.x, m_ViewportSize.y);
        m_CameraController.OnResize(m_ViewportSize.x, m_ViewportSize.y);
    }
    // HZ_WARN("viewport size: {0}, {1}", viewportSize.x, viewportSize.y);

    uint32_t textureid = m_FrameBuffer->GetColorAttachmentRendererID();
    ImGui::Image((void *)textureid, ImVec2(m_ViewportSize.x, m_ViewportSize.y), ImVec2(0, 1), ImVec2(1, 0));
    ImGui::End();
    ImGui::PopStyleVar();

    ImGui::End();
}

void EditorLayer::OnEvent(Hazel::Event &e)
{
    m_CameraController.OnEvent(e);
}
} // namespace Hazel